package gorefit

import (
	"container/list"
	"github.com/moovweb/gokogiri"
	"github.com/moovweb/gokogiri/html"
	"github.com/moovweb/gokogiri/xml"
	"strings"
)

type Document struct {
	rawBody  string
	xmlBody  *html.HtmlDocument
	messages list.List
}

func (self *Document) PushMessage(msg *Message) {
	self.messages.PushBack(msg)
}

func (self *Document) PopMessage() *Message {
	el := self.messages.Front()
	if el == nil {
		return nil
	}
	msg := el.Value.(*Message)
	self.messages.Remove(el)
	return msg
}

func (self *Document) PopError() *Message {
	for el := self.messages.Front(); el != nil; el = el.Next() {
		err, ok := el.Value.(*Message)
		if ok && err.Type == ERROR {
			self.messages.Remove(el)
			return err
		}
	}
	return nil
}

func (self *Document) SetBody(text string, ignoreencoding bool) error {
	self.rawBody = text

	var err error

	self.xmlBody, err = gokogiri.ParseHtml([]byte(self.rawBody))
	if err != nil {
		self.PushMessage(NewError(err.Error()))
		return err
	}
	if ignoreencoding == false && self.xmlBody.MetaEncoding() == "iso-8859-1" {
		self.SetBody(toUtf8([]byte(text)), true)
	}
	return nil
}

func (self *Document) Get(xpath string) []xml.Node {
	if self.xmlBody.Root() == nil {
		self.PushMessage(NewError("Document.Get: Not a document"))
		return nil
	}
	nodes, err := self.xmlBody.Root().Search(xpath)
	var result = make([]xml.Node, len(nodes))
	if err != nil {
		self.PushMessage(NewError("Document.Get:" + err.Error()))
		return nil
	} else {
		for i, n := range nodes {
			result[i] = n
		}
	}

	if len(nodes) == 0 {
		self.PushMessage(NewDebug("Could not find any nodes matching XPath \"" + xpath + "\""))
	}

	return result
}

func (self *Document) Exists(xpath string) bool {
	if self.xmlBody.Root() == nil {
		self.PushMessage(NewError("Document.Exists: Not a document"))
		return false
	}
	nodes, err := self.xmlBody.Root().Search(xpath)
	if err != nil {
		self.PushMessage(NewError("Document.Exists:" + err.Error()))
		return false
	}
	return (len(nodes) > 0)
}

func (self *Document) Remove(xpath string) int {
	if self.xmlBody.Root() == nil {
		self.PushMessage(NewError("Document.Remove: Not a document"))
		return 0
	}

	nodes, err := self.xmlBody.Root().Search(xpath)
	count := len(nodes)
	if err != nil {
		self.PushMessage(NewError("Document.Remove:/parse" + err.Error()))
		return 0
	} else {
		for _, n := range nodes {
			n.Remove()
		}
	}

	if count == 0 {
		self.PushMessage(NewDebug("No nodes to remove when removing " + xpath))
	}

	// Update raw body
	self.updateRawBody()

	return count
}

func (self *Document) Replace(xpath string, newdata interface{}) int {

	if self.xmlBody.Root() == nil {
		self.PushMessage(NewError("Document.Replace: Not a document"))
		return 0
	}
	nodes, err := self.xmlBody.Root().Search(xpath)
	count := len(nodes)
	if err != nil {
		self.PushMessage(NewError("Document.Replace:" + err.Error()))
		return 0
	} else if count == 0 {
		self.PushMessage(NewWarning("Document.Replace: No matches for xpath: " + xpath))
	} else {
		for _, n := range nodes {
			if ns, ok := newdata.(string); ok {
				self.PushMessage(NewDebug("Replacing " + xpath + "(" + n.String() + ") with " + ns))
				err = n.Replace(ns)
			} else if nn, ok := newdata.(xml.Node); ok {
				self.PushMessage(NewDebug("Replacing " + xpath + "(" + n.String() + ") with " + nn.Path()))
				err = n.Replace(nn)
			} else if nsn, ok := newdata.([]xml.Node); ok {
				if len(nsn) > 0 {
					self.PushMessage(NewDebug("Replacing " + xpath + "(" + n.String() + ") with slice[0] " + nsn[0].Path()))
					err = n.Replace(nsn[0])
				} else {
					self.PushMessage(NewWarning("Not replacing " + xpath + "(" + n.String() + ") with anything!"))
				}
			} else {
				self.PushMessage(NewDebug("Replacing " + xpath + "(" + n.String() + ") with unkown type"))
				err = n.Replace(newdata)
			}
			if err != nil {
				self.PushMessage(NewError(err.Error()))
				return 0
			}
		}
	}

	// Update raw body
	self.updateRawBody()

	return count
}

func (self *Document) Append(xpath string, newdata interface{}) int {

	if self.xmlBody.Root() == nil {
		self.PushMessage(NewError("Document.Append: Not a document"))
		return 0
	}
	nodes, err := self.xmlBody.Root().Search(xpath)
	count := len(nodes)
	if err != nil {
		self.PushMessage(NewError("Document.Append:" + err.Error()))
		return 0
	} else {
		for _, n := range nodes {
			n.AddChild(newdata)
		}
	}

	// Update raw body
	self.updateRawBody()

	if count == 0 {
		self.PushMessage(NewDebug("Unable to append data. Can't find " + xpath))
	}
	return count
}

func (self *Document) updateRawBody() {
	self.rawBody = self.xmlBody.String()
}

func (self *Document) String() string {
	return self.xmlBody.String()
}

func (self *Document) RewriteLinks(fromdomain string, todomain string) {
	links := self.Get("//a")
	for _, n := range links {
		// Only rewrite link if the href starts with fromdomain
		if n.Attribute("href") != nil && len(n.Attribute("href").Value()) >= len(fromdomain) && n.Attribute("href").Value()[:len(fromdomain)] == fromdomain {
			n.Attribute("href").SetValue(strings.Replace(n.Attribute("href").Value(), fromdomain, todomain, 1))
		}
	}
}

func toUtf8(iso8859_1_buf []byte) string {
	buf := make([]rune, len(iso8859_1_buf))
	for i, b := range iso8859_1_buf {
		buf[i] = rune(b)
	}
	return string(buf)
}
